#! /usr/bin/env python
#
# Google command line interface
# Copyright (C) Deryck Hodge 2005
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


import cmd, sys, re, getopt

from libgcli import __version__, __copyright__
from libgcli.params import Params
from libgcli.search import Search
from libgcli.display import Display


search_history = []


class Gcli(cmd.Cmd):
    
    def __init__(self):
        """ Gcli() is initialized when gcli is run as an
            interactive session.
        """

        cmd.Cmd.__init__(self)
        self.intro = 'gcli version ' + __version__ + '\n' + __copyright__ + \
                        '\nFor a list of useful commands, type "help".'
        self.prompt = 'gcli> '
        self.params = Params()

    # Rewriting help from cmd.py for my own help message
    def do_help(self, arg):
        """A basic help menu function."""

        if arg:
            # XXX check arg syntax
            try:
                func = getattr(self, 'help_' + arg)
            except AttributeError:
                try:
                    doc=getattr(self, 'do_' + arg).__doc__
                    if doc:
                        self.stdout.write("%s\n"%str(doc))
                        return
                except AttributeError:
                    pass
                self.stdout.write("%s\n"%str(self.nohelp % (arg,)))
                return
            func()
        else:
            print """
The following is a list of available commands:
(Type "help <command>" for more info.)

Commands:
----------------------------------------------
search, s
more, m
last, l
history, h
response
results
query
count
start
filtered
saved
display
max
set
browse
cache
suggest
exit, quit, q
"""

    def default(self, args):
        """
To begin searching Google, simply enter your query
at the prompt.  Standard Google syntaxes and format apply.

For example, 'gcli> some search terms' will search Google for the words 'some'
and 'search' and 'terms'.  'gcli> "some search terms"' will search Google
for the phrase "some search terms".

You may append the word search or the letter s to specify that what
follows is a query.  This may be needed if you search starts with
a reserved command in gcli. For example, if searching for 'history',
which is a command in gcli, try "search history" or "s history" at
the prompt.
"""
        self.params.query = args
        self.previous = False

        # Check history, so we don't init a new Search()
        if search_history != []:
            for search in search_history:
                # Ensure the conditions of the search are the same
                if self.params.query in search.keys() and (search[self.params.query][1] 
                        == (self.params.filter, self.params.safe_search, 
                                self.params.max_results, self.params.index_start)):
                        self.previous = True

        if self.previous == True:
            for s in search_history:
                if self.params.query in s.keys():
                    search = s[self.params.query][2]
                    results = s[self.params.query][0]
        else:
            search = Search(self.params)
            results = search.results()
            search_history.append({self.params.query : (results, (self.params.filter, 
                    self.params.safe_search, self.params.max_results, self.params.index_start), search)})

        d = Display(self.params.display, results, search)

    do_search = do_s = default


    def do_response(self, args):
        """
Usage: response history[i]
Display the response object returned from the search.
Probably only useful for the ultra curious, to see what
doGoogleSearch has returned.
"""
        if 'history[' in args:
            try:
                index = int(args.split('[')[1][:1])
                search = search_history[index].values()[0][2]
                print search.response()
            except IndexError:
                print 'Index out of range.'
        else:
            print 'Usage: response history[i]'
            

    def do_results(self, args):
        """
Usage: results history[i]
Display the results object returned from the search.
Probably only useful for the ultra curious, to see the
resultElements returned by doGoogleSearch.
"""
        if 'history[' in args:
            try:
                index = int(args.split('[')[1][:1])
                print search_history[index].values()[0][0]
            except IndexError:
                print 'Index out of range.'
        else:
            print 'Usage: results history[i]'


    def do_more(self, args):
        """
Type 'more' to return another results set of the most recently 
run search query.  Number of results is dependent on the 'max'
option's setting.
"""
        if len(search_history) > 0:
            n = len(search_history) - 1
            # Search phrase is the key to all previous searches
            last_search = search_history[n]

            # Increment the last query by 10 and search again
            if last_search.values()[0][1][3] == 0:
                start = 10
            else:
                start = last_search.values()[0][1][3] + 10

            prev_query = last_search.keys()[0]
            self.params.start_at(start)
            more_search = Search(self.params)
            more_results = more_search.results()
            search_history.append({prev_query : (more_results, (self.params.filter, 
                        self.params.safe_search, self.params.max_results, self.params.index_start), more_search)}) 
                
            d = Display(self.params.display, more_results, more_search)
        else:
            print "Search history is empty."

    do_m = do_more


    def do_query(self, args):
        """
Display the search terms of the previous query.
"""
        n = len(search_history) - 1
        last_search = search_history[n]
        prev_query = last_search.keys()[0]
        print prev_query


    def do_last(self, args):
        """
Print the results of the last query again.  If you change your
display format and want to reprint in the new format,
this can be a useful command.
"""
        last_search = search_history[len(search_history) -1]
        d = Display(self.params.display, last_search.values()[0][0], last_search.values()[0][2])

    do_l = do_last
        

    def do_history(self, args):
        """
history is stored as an array of
[ query, resultElements, params, search_obj ]
and may be accessed via index... 
history[0], history[1], etc.

Or, type "history" at the prompt for a list of 
previous query terms.
"""
        if '[' in args:
            try:
                index = args.strip('[')
                index = index.strip(']')
                print search_history[int(index)]
            except IndexError:
                print 'Index out of range.'
        else:
            for search in search_history:
                print search.keys()

    do_h = do_history


    def do_count(self, args):
        """
Display the total number of possible results for the last query,
if run with no arguments.  Display the number of possible results for the
following query, if used in conjunction with a query (i.e. "count samba" will
return the number of possible results for the query "samba".
""" 
        if len(args) <= 0:
            if len(search_history) > 0:
                last_search = search_history[len(search_history) - 1]
                self.params.query = last_search.keys()[0]
                print Search(self.params).count()
            else:
                print "Search history is empty."
        else:
            self.params.query = "".join(args)
            r = Search(self.params)
            results = r.results()
            print r.count()
            search_history.append({self.params.query : (results, (self.params.filter, 
                    self.params.safe_search, self.params.max_results, self.params.index_start))})


    def do_filtered(self, args):
        """
Returns True if duplicate page filtering is turned on.  Returns False
if duplicate page filtering is turned off.
"""
        print self.params.filter


    def do_saved(self, args):
        """
Returns True if safe search is turned on.  Returns false if
safe search is turned off.
"""
        print self.params.safe_search


    def do_display(self, args):
        """
Prints the current display format.
"""
        print self.params.display


    def do_max(self, args):
        """
Prints the current max results setting.
"""
        print self.params.max_results


    def do_set(self, args):
        """
Set search parameters.  Usage: set [param] [value].
The following parameters can be set from the interactive
prompt: "filter", "safe", "max", "display".

filter and safe take booleon values (True or False).
max takes an integer as its value.
display takes one of the allowed display formats (brief, 
all, url, title, excerpt, google).
"""

        breaks = re.compile('\s')
        args = breaks.split(args)

        if args[0] == 'filter':
            if args[1].lower() == 'false':
                self.params.filter = False
            elif args[1].lower() == 'true': 
                self.params.filter = True
        elif args[0] == 'safe':
            if args[1].lower() == 'false':
                self.params.safe_search = False
            if args[1].lower() == 'true':
                self.params.safe_search = True
        elif args[0] == 'max':
            self.params.max_results = int(args[1])
        elif args[0] == 'display':
            if args[1] in ['url', 'title', 'excerpt', 'brief', 'all', 'google']:
                self.params.display = args[1]
            else:
                print 'display must be: "url", "title", "excerpt", "brief", "all", or "google".'

            
    def do_start(self, args):
        """
Display the start number for where the current results
fall with the total results possible for most recent query.
"""
        print self.params.index_start


    def do_browse(self, args):
        """
Requires lynx.  If installed, the browse command opens lynx to browse 
from within gcli.  Usage: browse [url]
"""
        import webbrowser
        try:
            browser = webbrowser.get('lynx')
            browser.open(args)
        except:
            print 'Lynx must be installed to browse from gcli.'

    def do_cache(self, args):
        """
Fetch a url from Google cache.
Type "cache URL" where URL is the page you want from cache.
"""
        from libgcli.cache import GoogleCache
        # Don't use the global params object.  We don't want
        # to override the current query
        cache_params = self.params
        cache_params.query = args
        cached_page = GoogleCache(cache_params).get_page()
        print cached_page


    def do_suggest(self, args):
        """
Get a spelling suggestion from Google.
Type "suggest WORD" where WORD is the word to check  
against Google's spelling suggestions.
"""
        from libgcli.suggest import SpellRequest
        # Don't use the global params object.  We don't want
        # to override the current query
        sp = self.params
        sp.query = args
        suggestion = SpellRequest(sp).suggest()

        if suggestion:
            print suggestion
        else:
            print 'Nothing to suggest for ' + args + '.'


    def do_exit(self, args):
        """
Type "exit", "quit", or "q" to quit gcli.
"""
        sys.exit()

    do_quit = do_exit
    do_q = do_exit


    def emptyline(self):
        self.prompt


def usage():
    print """Usage: gcli [OPTIONS] -d [DISPLAY] QUERY

Options:
  -?, -h, --help                  show this help screen
  -m, --max-number=[NUMBER]       max number of results to return 
  -f  --filter                    turn on duplicate page filtering
  -s  --safe-search               enable safe search
  -n  --num-results               list estimated number of possible results (and exit)
  -v --version                    print version (and exit)

Display:
  -d, --display=[DISPLAY]         set the format for displaying search results

  The default is 'brief', which prints the titles and urls of each result.
  Other settings are: 'url', 'title', 'excerpt', 'all', and 'google'.

Query:
  Multiple word queries must be in single quotes.  Phrases within a multi word
  query may be specified by double quotes.  For example:

  'deryck hodge samba' will search for deryck AND hodge AND samba
  '"deryck hodge" samba' will search for the phrase 'deryck hodge' AND samba

  The usual Google syntaxes are supported, though the Google API only supports
  a subset of special syntaxes.  See http://www.google.com/apis/reference.html#2_2
  for more on the API implementation of special syntaxes.

Defaults:
  gcli defaults to 10 results per query.  Duplicate page filtering and safe
  search are turned off by default.  

  Defaults may be changed with the flags above.  To change the defaults for 
  all your searches, add a .gcli directory to your home directory and include 
  a "config" file.  See the examples directory in the gcli source for config 
  file info.

Interactive:
  Run gcli with no arguments to enter the interactive prompt.  Type "help"
  at the prompt for more info.

Example searches:
  The first 100 urls on samba development...

        gcli -m 100 -d url 'samba development'

  And with samba development as a phrase...

        gcli -m 100 -d url '"samba development"'

  The same search, filtering duplicate pages...

        gcli -m 100 -f -d url '"samba development"'

"""


def main():
    if len(sys.argv) > 1:

        p = Params()

        try:
            opts, args = getopt.getopt(sys.argv[1:], '?hvfsenm:d:', ['help', 
                                'version', 'max-number=', 'display='])
        except getopt.GetoptError:
            # Print help info and exit.
            usage()
            sys.exit(2)

        for opt, value in opts:
            if opt in ('-?', '-h', '--help'):
                usage()
                sys.exit(0)
            elif opt in ('-v', '--version'):
                print __version__
                sys.exit(0)
            elif opt in ('-f'):
                p.filter = False
            elif opt in ('-s'):
                p.safe_search = True
            elif opt in ('-m', '--max-number'):
                p.max_results = int(value)
            elif opt in ('-n'):
                p.count = True
            elif opt in ('-d', '--display'):
                p.display = value

        if len(args) > 1: 
            usage()
            sys.exit(2)
        else:
            for arg in args:
                p.query = arg

        # If running -n, print number and exit...
        if p.count:
            print Search(p).count()
            sys.exit(0)
        # ...otherwise pass results on to be displayed.
        else:
            search = Search(p)
            results = search.results()
            d = Display(p.display, results, search)

    else:
        cli = Gcli()
        cli.cmdloop()


if __name__ == "__main__":
    main()

